#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>


#define SIZE 512
#define ZORE '\0'
#define SEP " "
#define NUM 32
#define SkipPath(p) do{p+=strlen(p)-1;while(*p!='/') p--;}while(0)

const char*  GetHome()
{
    char* home=getenv("HOME");
    if(home==NULL) return "/";
    return home;
}

void Die()
{
    exit(0);
}
//获取用户名
const char* GetUserName()
{
    char* name=getenv("USER");
    if(name==NULL) return "None";
    return name;
}
//获取主机名
const char* GetHostName()
{
    char* hostname=getenv("HOSTNAME");
    if(hostname==NULL) return "None";
    return hostname;
}
//获取当前工作路径
const char* GetCwd()
{
    char* cwd=getenv("PWD");
    if(cwd==NULL) return "cwd";
    return cwd;
}
void MakeCommandLineAddPrint()
{
    char line[SIZE];
    const char* name=GetUserName();
    const char* hostname=GetHostName();
    const char* cwd=GetCwd();
    SkipPath(cwd);
    snprintf(line,sizeof(line),"[%s@%s %s]> ",name,hostname,strlen(cwd)==1? "/" : cwd+1);
    //snprintf将给定的数据以特定的格式，将数据交给内存line的变量中
    printf("%s",line);
    //在Linux中不加\n就是默认存储在了缓冲区中
    //所以此时需要冲刷缓冲区
    fflush(stdout);//冲刷缓冲区
}
//获取用户输入的命令行参数
int GetUserCommand(char *Command,size_t n)
{
    char *s=fgets(Command,n,stdin);
    if(s==NULL) return -1;//如果没有输入，就返回-1
    Command[strlen(Command)-1]=ZORE;
    //printf("echo:%s\n",Command);
    return strlen(Command);//如果读取成功了，就返回读取到命令的字符串的大小

}
char *gArgv[NUM];
void SplitCommand(char Command[],size_t n)
{
    gArgv[0]=strtok(Command,SEP);
    int index=1;
    while((gArgv[index++]=strtok(NULL,SEP)));
    //故意写为“=”，表示先赋值，再判断，分割之后，strtok会返回NULL，刚好让gArgv最后一个元素为NULL，并且while判断结束
}

int lastcode=0;
//执行命令
void ExecuteCommand()
{
    //int i=0;
   // for(i=0;gArgv[i];i++)
   // {
   //     printf("gArgv[%d]:%s\n",i,gArgv[i]);
   // }
    //n.执行命令
    pid_t id=fork();
    if(id<0) Die();
    else if(id==0)//子进程
    {
        //进程替换
        //我想让执行系统中的命令，传入系统的默认指定的命令
        //所以只需要在默认系统的环境变量中找就行
        //利用exec函数，中的execp
        //而且命令行参数均是数组，所以用v
        execvp(gArgv[0],gArgv);
        exit(errno);
    }
    else//父进程
    {
        int status=0;
        pid_t rid=waitpid(id,&status,0);
        if(rid>0)
        {
            lastcode=WEXITSTATUS(status);
            if(lastcode!=0)//表示没有正常退出
                printf("%s:%s:%d\n",gArgv[0],strerror(lastcode),lastcode);

        }
    }
}

char cwd[SIZE*2];

//是cd的内建命令
void Cd()
{
    //执行cd：cd后面为空 就直接为家目录
    //cd 后面由..就返回上级目录
    const char* path=gArgv[1];
    if(path==NULL) GetHome();
    chdir(path);

    //刷新环境变量
    char temp[SIZE*2];
    //获取当前新的环境变量
    getcwd(temp,sizeof(temp));
    //更新
    snprintf(cwd,sizeof(cwd),"PWD=%s",temp);
    putenv(cwd);
    
}

//检查是否是内建命令
int CheckBuildIn()
{
    int yes=0;
    const char* enter_cmd=gArgv[0];
    if(strcmp(enter_cmd,"cd")==0)
    {
        yes=1;
        Cd();
    }
    else if(strcmp(enter_cmd,"echo")==0)
    {
        yes=1;
        printf("%d\n",lastcode);
    }
    return yes;
}

int main()
{
    int quit =0;
    while(!quit)
    {
        //1.需要输出一个命令行
        //printf("Name:%s\n",GetUserName());
        //printf("HostName:%s\n",GetHostName());
        //printf("Cwd:%s\n",GetCwd());
        //生成为命令行提示符
        MakeCommandLineAddPrint();
        //此处没有栈帧销毁造成的后果，数据写了了内存，将存储数据的内存已经填好
        //2.获取用户命令行字符串
        char UserCommand[SIZE];
        int n=GetUserCommand(UserCommand,sizeof(UserCommand));
        if(n<=0) return 1;
        //3.命令行字符串分割
        SplitCommand(UserCommand,sizeof(UserCommand));
        //4.检查是否为内建命令
        n=CheckBuildIn();
        if(n) continue;//是内建命令的话就不再进行运行后序代码了，在检查是否为内建命令的函数中包含了内建命令的执行。
        //n.执行命令     
        ExecuteCommand();
    }
    return 0;
}
